/*
 * HSM Proxy Project.
 * Copyright (C) 2013 FedICT.
 * Copyright (C) 2011-2012 AGIV.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.hsm.client;

import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.xml.crypto.dsig.Reference;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSEncryptionPart;
import org.apache.ws.security.WSSConfig;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecSignature;
import org.apache.ws.security.message.WSSecTimestamp;

/**
 * A JAX-WS SOAP handler that delivers the required WS-Security.
 * 
 * @author Frank Cornelis
 * 
 */
public class WSSecuritySOAPHandler implements SOAPHandler<SOAPMessageContext> {

	private static final Log LOG = LogFactory
			.getLog(WSSecuritySOAPHandler.class);

	private final PrivateKey privateKey;

	private final X509Certificate certificate;

	public WSSecuritySOAPHandler(PrivateKey privateKey,
			X509Certificate certificate) {
		this.privateKey = privateKey;
		this.certificate = certificate;
	}

	@Override
	public boolean handleMessage(SOAPMessageContext context) {
		Boolean outboundProperty = (Boolean) context
				.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		if (true == outboundProperty.booleanValue()) {
			try {
				handleOutboundMessage(context);
			} catch (Exception e) {
				throw new ProtocolException(e);
			}
		}
		return true;
	}

	private void handleOutboundMessage(SOAPMessageContext context)
			throws SOAPException, WSSecurityException {

		if (null == this.privateKey) {
			LOG.warn("no adding a WS-Security header");
			return;
		}

		SOAPMessage soapMessage = context.getMessage();
		SOAPPart soapPart = soapMessage.getSOAPPart();
		soapMessage.getSOAPHeader();
		WSSecHeader wsSecHeader = new WSSecHeader();
		wsSecHeader.setMustUnderstand(true);
		wsSecHeader.insertSecurityHeader(soapPart);

		WSSecTimestamp wsSecTimeStamp = new WSSecTimestamp();
		wsSecTimeStamp.prepare(soapPart);
		wsSecTimeStamp.prependToHeader(wsSecHeader);

		WSSecurityCrypto crypto = new WSSecurityCrypto(this.privateKey,
				this.certificate);
		WSSConfig wssConfig = new WSSConfig();
		WSSecSignature sign = new WSSecSignature(wssConfig);
		sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
		sign.prepare(soapPart, crypto, wsSecHeader);
		String bstId = sign.getBSTTokenId();
		sign.appendBSTElementToHeader(wsSecHeader);
		sign.setDigestAlgo("http://www.w3.org/2001/04/xmlenc#sha256");
		sign.setSignatureAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
		Vector<WSEncryptionPart> signParts = new Vector<WSEncryptionPart>();
		signParts.add(new WSEncryptionPart(wsSecTimeStamp.getId()));
		signParts.add(new WSEncryptionPart(WSConstants.ELEM_BODY,
				WSConstants.URI_SOAP12_ENV, ""));
		signParts.add(new WSEncryptionPart(bstId));
		sign.addReferencesToSign(signParts, wsSecHeader);
		List<Reference> referenceList = sign.addReferencesToSign(signParts,
				wsSecHeader);
		sign.computeSignature(referenceList, false, null);
	}

	@Override
	public boolean handleFault(SOAPMessageContext context) {
		return true;
	}

	@Override
	public void close(MessageContext context) {
	}

	@Override
	public Set<QName> getHeaders() {
		return null;
	}
}
